package com.mrlong.mlcamerac.videoEncoder;

import android.media.MediaCodec;
import android.media.MediaFormat;

import com.mrlong.mlcamerac.push.Encoder;
import com.mrlong.mlcamerac.util.ColorUtil;
import com.mrlong.mlcamerac.util.YUV420spRotateUtils;
import com.mrlong.mlcamerac.yuv.YuvEngineWrap;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ArrayBlockingQueue;

import static android.media.MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
import static android.media.MediaCodec.BUFFER_FLAG_KEY_FRAME;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;


public class MVideoEncoder {
    private final static String TAG = "MeidaCodec==>";
    private static int yuvqueuesize = 5;
    public static final String VCODEC = "video/avc";
    //待解码视频缓冲队列，静态成员！
    private static ArrayBlockingQueue<byte[]> YUVQueue = new ArrayBlockingQueue<byte[]>(yuvqueuesize);
    private int TIMEOUT_USEC = 10000;

    private MediaCodec mediaCodec;
    int m_width;
    int m_height;
    int m_framerate;

    public byte[] configbyte;
    private Thread encoderThread;
    private final int colorFormat;
    private final MediaFormat mediaFormat;

    public void putYUVData(byte[] buffer) {
        if (YUVQueue.size() >= 5) {
            YUVQueue.poll();
        }
        YUVQueue.add(buffer);
    }

    public interface OnMediaInfoListener {
        void pushVideoData(byte[] data, boolean keyframe);

        void pushSPSPPS(byte[] sps, byte[] pps);
    }

    public OnMediaInfoListener onMediaInfoListener;

    public void setOnMediaInfoListener(OnMediaInfoListener onMediaInfoListener) {
        this.onMediaInfoListener = onMediaInfoListener;
    }

    public void changeBit(int bit) {
        if (0 != bit) {
            try {
                mediaCodec.getInputFormat().setInteger(MediaFormat.KEY_BIT_RATE, bit);
                mediaCodec.getOutputFormat().setInteger(MediaFormat.KEY_BIT_RATE, bit);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param width
     * @param height
     * @param VFPS
     * @param bitrate
     */
    public MVideoEncoder(int width, int height, int VFPS, int bitrate) {

        m_width = width;
        m_height = height;
        m_framerate = VFPS;
        mediaFormat = MediaFormat.createVideoFormat(VCODEC, width, height);
        //颜色值 可能导致图像颜色不对
        colorFormat = ColorUtil.getMColor();

        //Camera  推荐 21
        //Camera2 推荐 19

        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, 19);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, VFPS);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
        mediaFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
        //mediaFormat.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 40);
        mediaFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);

        try {
            mediaCodec = MediaCodec.createEncoderByType("video/avc");
        } catch (IOException e) {
            e.printStackTrace();
        }
        //配置编码器参数
        mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        //启动编码器
        mediaCodec.start();

    }


    private void stopEncoder() {
        try {
            if (null != mediaCodec) {
                mediaCodec.stop();
                mediaCodec.release();
            }
            if (encoderThread.isInterrupted()) {
                encoderThread.interrupt();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public boolean isRuning = false;

    public void stopThread() {
        isRuning = false;
        try {
            YuvEngineWrap.newInstance().stopYuvEngine();
            stopEncoder();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String currentCameraLocation = "1";

    public void setFrontOrBack(String location) {
        this.currentCameraLocation = location;
    }

    public void startEncoderThread() {
        YuvEngineWrap.newInstance().startYuvEngine();
        encoderThread = new Thread(new Runnable() {

            @Override
            public void run() {
                isRuning = true;
                byte[] input = null;
                long pts = 0;
                long generateIndex = 0;

                while (isRuning) {
                    //访问MainActivity用来缓冲待解码数据的队列
                    if (YUVQueue.size() > 0) {
                        //从缓冲队列中取出一帧
                        input = YUVQueue.poll();
                        //***********************如果使用Camera 请设置NV21格式 并使用下面代码转行YUV420***********************
                        //byte[] yuv420sp = new byte[m_width * m_height * 3 / 2];
                        //YuvEngineWrap.newInstance().Nv21ToNv12(input, yuv420sp, m_width, m_height);
                        //NV21ToNV12(input, yuv420sp, m_width, m_height);
                        //旋转方向
                        //input = YUV420spRotateUtils.rotateYUVDegree270(yuv420sp, m_height, m_width);
                        //***********************如果使用Camera 请设置NV21格式 并使用上面代码转行YUV420***********************

                        //***************如果使用Camera2 设置YUV_420_888格式 并使用下面代码选择角度  注意前置后置区别***********
                        if ("1" == currentCameraLocation) {
                            input = YUV420spRotateUtils.rotateYUVDegree270(input, m_height, m_width);
                        } else if ("0" == currentCameraLocation) {
                            input = YUV420spRotateUtils.frameMirror(input, m_height, m_width);
                            input = YUV420spRotateUtils.rotateYUVDegree90(input, m_height, m_width);
                        }
                        //***************如果使用Camera2 设置YUV_420_888格式 并使用上面代码选择角度  注意前置后置区别***********
                    }

                    if (input != null) {
                        try {
                            //编码器输入缓冲区
                            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
                            //编码器输出缓冲区
                            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
                            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
                            if (inputBufferIndex >= 0) {
                                pts = computePresentationTime(generateIndex);
                                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                                inputBuffer.clear();
                                //把转换后的YUV420格式的视频帧放到编码器输入缓冲区中
                                inputBuffer.put(input);
                                mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0);
                                generateIndex += 1;
                            }

                            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                            while (outputBufferIndex >= 0) {
                                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                byte[] outData = new byte[bufferInfo.size];
                                outputBuffer.get(outData);
                                if (bufferInfo.flags == BUFFER_FLAG_CODEC_CONFIG) {
                                    configbyte = new byte[bufferInfo.size];
                                    configbyte = outData;
                                } else if (bufferInfo.flags == BUFFER_FLAG_KEY_FRAME) {
                                    byte[] keyframe = new byte[bufferInfo.size + configbyte.length];
                                    System.arraycopy(configbyte, 0, keyframe, 0, configbyte.length);
                                    //把编码后的视频帧从编码器输出缓冲区中拷贝出来
                                    System.arraycopy(outData, 0, keyframe, configbyte.length, outData.length);
                                    if (null != onMediaInfoListener)
                                        onMediaInfoListener.pushVideoData(keyframe, true);
                                } else {
                                    if (null != onMediaInfoListener)
                                        onMediaInfoListener.pushVideoData(outData, true);
                                }
                                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                            }

                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    } else {
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        encoderThread.start();

    }

    private void NV21ToNV12(byte[] nv21, byte[] nv12, int width, int height) {
        if (nv21 == null || nv12 == null) return;
        int framesize = width * height;
        int i = 0, j = 0;
        System.arraycopy(nv21, 0, nv12, 0, framesize);
        for (i = 0; i < framesize; i++) {
            nv12[i] = nv21[i];
        }
        for (j = 0; j < framesize / 2; j += 2) {
            nv12[framesize + j - 1] = nv21[j + framesize];
        }
        for (j = 0; j < framesize / 2; j += 2) {
            nv12[framesize + j] = nv21[j + framesize - 1];
        }
    }

    /**
     * Generates the presentation time for frame N, in microseconds.
     */
    private long computePresentationTime(long frameIndex) {
        return 132 + frameIndex * 1000000 / m_framerate;
    }
}